package com.wh.maker.template;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.wh.maker.meta.Meta;
import com.wh.maker.meta.enums.FileGenerateTypeEnum;
import com.wh.maker.meta.enums.FileTypeEnum;
import com.wh.maker.template.enums.FileFilterRangeEnum;
import com.wh.maker.template.enums.FileFilterRuleEnum;
import com.wh.maker.template.model.*;

import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TemplateMaker {
    public static void main(String[] args) {
//        Meta meta = new Meta();
//        meta.setName("springboot-init");
//        meta.setDescription("springboot初始化项目");
//
//
//        String projectPath = Paths.get(System.getProperty("user.dir")).getParent().toString();
//        //  springboot-init-old-master
//        String originProject = new File(projectPath) + File.separator + "wh-generate-demo-projects/springboot-init/springboot-init-old-master";
//
//        String inputFilePath1 = "src/main/java/com/yupi/project/common";
//        String inputFilePath2 = "src/main/resources/application.yml";
//        List<String> inputFilePathList = Arrays.asList(inputFilePath1, inputFilePath2);
//
//
//        Meta.ModelConfig.ModelInfo modelInfo = new Meta.ModelConfig.ModelInfo();
//        modelInfo.setFieldName("className");
//        modelInfo.setType("string");
//
//        String searchStr = "BaseResponse";
//
//        // 文件过滤器配置
//        TemplateMakerFileConfig templateMakerFileConfig = new TemplateMakerFileConfig();
//        TemplateMakerFileConfig.FileInfoConfig fileInfoConfig1 = new TemplateMakerFileConfig.FileInfoConfig();
//        fileInfoConfig1.setPath(inputFilePath1);
//
//        List<FileFilterConfig> fileFilterConfigList = new ArrayList<>();
//        FileFilterConfig fileFilterConfig = FileFilterConfig.builder()
//                .range(FileFilterRangeEnum.FILE_CONTENT.getValue())
//                .rule(FileFilterRuleEnum.CONTAINS.getValue())
//                .value("Base")
//                .build();
//
//        fileFilterConfigList.add(fileFilterConfig);
//        fileInfoConfig1.setFilters(fileFilterConfigList);
//        TemplateMakerFileConfig.FileInfoConfig fileInfoConfig2 = new TemplateMakerFileConfig.FileInfoConfig();
//        fileInfoConfig2.setPath(inputFilePath2);
//        templateMakerFileConfig.setFiles(Arrays.asList(fileInfoConfig1, fileInfoConfig2));
//
//        // 分组配置
//        TemplateMakerFileConfig.FileGroupConfig fileGroupConfig = new TemplateMakerFileConfig.FileGroupConfig();
//        fileGroupConfig.setCondition("outputText");
//        fileGroupConfig.setGroupKey("test");
//        fileGroupConfig.setGroupName("测试分组");
//        templateMakerFileConfig.setFileGroupConfig(fileGroupConfig);
//
//
//        // 模型参数配置
//        TemplateMakerModelConfig templateMakerModelConfig = new TemplateMakerModelConfig();
//        // - 模型组配置
//        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = new TemplateMakerModelConfig.ModelGroupConfig();
//        modelGroupConfig.setGroupKey("mysql");
//        modelGroupConfig.setGroupName("数据库配置");
//        templateMakerModelConfig.setModelGroupConfig(modelGroupConfig);
//        // - 模型配置
//        TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig1 = new TemplateMakerModelConfig.ModelInfoConfig();
//        modelInfoConfig1.setFieldName("url");
//        modelInfoConfig1.setType("String");
//        modelInfoConfig1.setDefaultValue("jdbc:mysql://localhost:3306/my_db");
//        modelInfoConfig1.setReplaceText("jdbc:mysql://localhost:3306/my_db");
//        TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig2 = new TemplateMakerModelConfig.ModelInfoConfig();
//        modelInfoConfig2.setFieldName("username");
//        modelInfoConfig2.setType("String");
//        modelInfoConfig2.setDefaultValue("root");
//        modelInfoConfig2.setReplaceText("root");
//        List<TemplateMakerModelConfig.ModelInfoConfig> modelInfoConfigList = Arrays.asList(modelInfoConfig1, modelInfoConfig2);
//        templateMakerModelConfig.setModels(modelInfoConfigList);

        //long id = makeTemplate(meta, templateMakerFileConfig, templateMakerModelConfig, null);
        //System.out.println(id);
    }

    public static long makeTemplate(Meta newMeta, String originProjectPath,TemplateMakerFileConfig templateMakerFileConfig, TemplateMakerModelConfig templateMakerModelConfig, TemplateMakerOutputConfig templateMakerOutputConfig, Long id) {

        //如果没有id则生成
        if (id == null) {
            id = IdUtil.getSnowflakeNextId();
        }

        //复制目录
        String projectPath = System.getProperty("user.dir");            //wh-generate-maker
        String tempDirPath = projectPath + File.separator + ".temp";   //wh-generate-maker/.temp
        String tempplatePath = tempDirPath + File.separator + id;      //wh-generate-maker/.temp/1234567890123456789

        //如果目标目录不存在才复制
        if (!FileUtil.exist(tempplatePath)) {
            FileUtil.mkdir(tempplatePath);
            FileUtil.copy(originProjectPath, tempplatePath, true);
        }

        //wh-generate-maker/.temp/1234567890123456789/springboot-init-old-master
        // 遍历/1下第一层文件，筛选出第一个目录
        String sourceRootPath = FileUtil.loopFiles(new File(tempplatePath),1,null)
                                        .stream()
                                        .filter(File::isDirectory)
                                        .findFirst()
                                        .orElseThrow(RuntimeException::new)
                                        .getAbsolutePath();

        //生成所有模板文件,拿到新增的fileinfoInfoList
        List<Meta.FileConfig.FileInfo> newFileInfoList = makeFileTemplates(templateMakerFileConfig, templateMakerModelConfig, sourceRootPath);

        //拿到新增的modelInfoList
        List<Meta.ModelConfig.ModelInfo> newModelInfoList = getModelInfoList(templateMakerModelConfig);

        /***
         * 三：生成元信息文件
         */
        //生成配置文件
        String metaOutputPath = tempplatePath + File.separator + "meta.json";

        //如果已有meta文件，则读取并追加
        if (FileUtil.exist(metaOutputPath)) {
            Meta oldMeta = JSONUtil.toBean(FileUtil.readUtf8String(metaOutputPath), Meta.class);
            BeanUtil.copyProperties(newMeta, oldMeta, CopyOptions.create().ignoreNullValue());
            newMeta = oldMeta;

            //追加配置参数
            List<Meta.FileConfig.FileInfo> fileInfoList = newMeta.getFileConfig().getFiles();
            fileInfoList.addAll(newFileInfoList);
            List<Meta.ModelConfig.ModelInfo> modelInfoList = newMeta.getModelConfig().getModels();
            modelInfoList.addAll(newModelInfoList);


            //配置去重
            newMeta.getFileConfig().setFiles(distinctFiles(fileInfoList));
            newMeta.getModelConfig().setModels(distinctModels(modelInfoList));

        } else {
            //构造配置参数
            Meta.FileConfig fileConfig = new Meta.FileConfig();
            newMeta.setFileConfig(fileConfig);
            fileConfig.setSourceRootPath(sourceRootPath);
            List<Meta.FileConfig.FileInfo> fileInfoList = new ArrayList<>();
            fileConfig.setFiles(fileInfoList);


            fileInfoList.addAll(newFileInfoList);


            Meta.ModelConfig modelConfig = new Meta.ModelConfig();
            newMeta.setModelConfig(modelConfig);
            List<Meta.ModelConfig.ModelInfo> modelInfoList = new ArrayList<>();
            modelConfig.setModels(modelInfoList);
            modelInfoList.addAll(newModelInfoList);
        }


        // 2. 额外的输出配置
//        if (templateMakerOutputConfig != null) {
//            // 文件外层和分组去重
//            if (templateMakerOutputConfig.isRemoveGroupFilesFromRoot()) {
//                List<Meta.FileConfig.FileInfo> fileInfoList = newMeta.getFileConfig().getFiles();
//                newMeta.getFileConfig().setFiles(TemplateMakerUtils.removeGroupFilesFromRoot(fileInfoList));
//            }
//        }

        // 3. 输出元信息文件
        FileUtil.writeUtf8String(JSONUtil.toJsonPrettyStr(newMeta), metaOutputPath);

        return id;
    }

    /**
     * 拿到新的模型配置列表
     * @param templateMakerModelConfig
     * @return
     */
    private static List<Meta.ModelConfig.ModelInfo> getModelInfoList(TemplateMakerModelConfig templateMakerModelConfig) {
        //临时存储的ModelInfo列表，用来存储新增的ModelInfo配置
        List<Meta.ModelConfig.ModelInfo> newModelInfoList = new ArrayList<>();
        if(templateMakerModelConfig == null) return newModelInfoList;


        //处理模型文件
        List<TemplateMakerModelConfig.ModelInfoConfig> models = templateMakerModelConfig.getModels();
        if(CollUtil.isEmpty(models)) return newModelInfoList;

        //转换为配置接收的ModelInfo对象
        List<Meta.ModelConfig.ModelInfo> inputModelInfoList = models.stream().map(modelInfoConfig -> {
            Meta.ModelConfig.ModelInfo modelInfo = new Meta.ModelConfig.ModelInfo();
            BeanUtil.copyProperties(modelInfoConfig, modelInfo);
            return modelInfo;
        }).collect(Collectors.toList());



        //如果是模型组
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        if (modelGroupConfig != null) {
            //复制变量
            Meta.ModelConfig.ModelInfo groupModelInfo = new Meta.ModelConfig.ModelInfo();
            BeanUtil.copyProperties(modelGroupConfig, groupModelInfo);

            //模型全部放到一个分组内
            groupModelInfo.setModels(inputModelInfoList);
            newModelInfoList.add(groupModelInfo);
        } else {
            newModelInfoList.addAll(inputModelInfoList);
        }
        return newModelInfoList;
    }

    /**
     * 生成所有模板文件，
     * @param templateMakerFileConfig
     * @param templateMakerModelConfig
     * @param fileInfoConfigList
     * @param sourceRootPath
     * @return  newFileInfoList 返回新增的文件信息列表
     */
    private static List<Meta.FileConfig.FileInfo> makeFileTemplates(TemplateMakerFileConfig templateMakerFileConfig, TemplateMakerModelConfig templateMakerModelConfig, String sourceRootPath) {

        //临时的文件列表，在这里存放过滤后的文件列表
        List<Meta.FileConfig.FileInfo> newFileInfoList = new ArrayList<>();


        if(templateMakerFileConfig==null){
            return newFileInfoList;
        }

        List<TemplateMakerFileConfig.FileInfoConfig> fileInfoConfigList = templateMakerFileConfig.getFiles();
        if(CollUtil.isEmpty(fileInfoConfigList)){
            return newFileInfoList;
        }

        //遍历传进来的文件列表
        for (TemplateMakerFileConfig.FileInfoConfig fileInfoConfig : fileInfoConfigList) {
            String inputFilePath = fileInfoConfig.getPath();
            //构造绝对路径
            String inputFileAbsolutePath = sourceRootPath + File.separator + inputFilePath;
            //过滤文件
            List<File> fileList = FileFilter.doFilter(inputFileAbsolutePath, fileInfoConfig.getFilters());
            fileList = fileList.stream()
                    .filter(file->!file.getAbsolutePath().contains(".ftl"))
                    .collect(Collectors.toList());
            //遍历过滤后的文件列表，生成模板文件
            for (File file : fileList) {
                Meta.FileConfig.FileInfo fileInfo = makeFileTemplate(templateMakerModelConfig, sourceRootPath, file,fileInfoConfig);
                newFileInfoList.add(fileInfo);
            }
        }

        // 如果是文件组
        TemplateMakerFileConfig.FileGroupConfig fileGroupConfig = templateMakerFileConfig.getFileGroupConfig();
        if (fileGroupConfig != null) {
            String condition = fileGroupConfig.getCondition();
            String groupKey = fileGroupConfig.getGroupKey();
            String groupName = fileGroupConfig.getGroupName();
            // 新增分组配置
            Meta.FileConfig.FileInfo groupFileInfo = new Meta.FileConfig.FileInfo();
            groupFileInfo.setType(FileTypeEnum.GROUP.getValue());
            groupFileInfo.setCondition(condition);
            groupFileInfo.setGroupKey(groupKey);
            groupFileInfo.setGroupName(groupName);
            // 文件全放到一个分组内
            groupFileInfo.setFiles(newFileInfoList);
            newFileInfoList = new ArrayList<>();
            newFileInfoList.add(groupFileInfo);
        }
        return newFileInfoList;
    }


    /**
     * 创建单个模板文件
     * @param templateMakerModelConfig  传入TemplateMakerModelConfig对象，
     * @param sourceRootPath   传入源根目录
     * @param inputFilePath    传入模板文件路径
     * @return
     */
    private static Meta.FileConfig.FileInfo makeFileTemplate(TemplateMakerModelConfig templateMakerModelConfig, String sourceRootPath, File inputFilePath,TemplateMakerFileConfig.FileInfoConfig fileInfoConfig) {


        // 要挖坑的文件绝对路径（用于制作模板）
        String fileInputAbsolutePath = inputFilePath.getAbsolutePath().replaceAll("\\\\", "/");
        String fileOutputAbsolutePath = fileInputAbsolutePath + ".ftl";


        //文件输入输出相对路径（用于生成配置）
        String fileInputPath = fileInputAbsolutePath.replace(sourceRootPath + "/", "");
        String fileOutputPath = fileInputPath + ".ftl";

        //使用字符串替换、生成模板文件
        String fileContent = null;

        //有模板文件都为动态生成
        boolean hasTemplateFile = FileUtil.exist(fileOutputAbsolutePath);
        //如果模板文件已经存在，说明不是第一次制作，则在模板基础上再次挖坑
        if (FileUtil.exist(fileOutputAbsolutePath)) {
            fileContent = FileUtil.readUtf8String(fileOutputAbsolutePath);
        } else {
            fileContent = FileUtil.readUtf8String(fileInputAbsolutePath);
        }

        // 支持多个模型：对同一个文件的内容，遍历模型进行多轮替换
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        String newFileContent = fileContent;
        String replacement;
        for (TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig : templateMakerModelConfig.getModels()) {
            // 不是分组
            if (modelGroupConfig == null) {
                replacement = String.format("${%s}", modelInfoConfig.getFieldName());
            } else {
                // 是分组
                String groupKey = modelGroupConfig.getGroupKey();
                // 注意挖坑要多一个层级
                replacement = String.format("${%s.%s}", groupKey, modelInfoConfig.getFieldName());
            }
            // 多次替换
            newFileContent = StrUtil.replace(newFileContent, modelInfoConfig.getReplaceText(), replacement);
        }

        //文件配置信息
        Meta.FileConfig.FileInfo fileInfo = new Meta.FileConfig.FileInfo();
        fileInfo.setInputPath(fileOutputPath);
        fileInfo.setOutputPath(fileInputPath);
        fileInfo.setType(FileTypeEnum.FILE.getValue());
        fileInfo.setGenerateType(FileGenerateTypeEnum.DYNAMIC.getValue());
        fileInfo.setCondition(fileInfoConfig.getCondition());

        //是否更改了文件内容
        boolean contentEquals = newFileContent.equals(fileContent);

        //是否存在模板文件
        if(!hasTemplateFile){
            //没有更改文件内容
            if(contentEquals){
                //输出路径 = 输入路径
                fileInfo.setOutputPath(fileInputPath);
                fileInfo.setGenerateType(FileGenerateTypeEnum.STATIC.getValue());
            }
            //更改了文件内容
            else {
                //没有模板文件，需要挖坑生成模板文件
                FileUtil.writeUtf8String(newFileContent, fileOutputAbsolutePath);
            }
        }else if(!contentEquals){
            //有模板文件，且增加了新坑，生成模板文件
            FileUtil.writeUtf8String(newFileContent, fileOutputAbsolutePath);
        }

        return fileInfo;
    }

    /**
     * Meta.FileConfig.FileInfo列表去重，支持文件分组，生成配置文件前处理
     * @param fileInfoList  传入Meta.FileConfig.FileInfo列表
     * @return  去重后的Meta.FileConfig.FileInfo列表
     */
    private static List<Meta.FileConfig.FileInfo> distinctFiles(List<Meta.FileConfig.FileInfo> fileInfoList) {

        //有分组的，以组为单位划分
        //groupKeyFileInfoListMap   根据groupKey进行分组，value为Meta.FileConfig.FileInfo列表
        Map<String, List<Meta.FileConfig.FileInfo>> groupKeyFileInfoListMap = fileInfoList.stream()
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getGroupKey()))
                .collect(Collectors.groupingBy(Meta.FileConfig.FileInfo::getGroupKey));

        //同组内文件配置合并
        List<Meta.FileConfig.FileInfo> resultList = new ArrayList<>();
        //取出一个同组
        for (Map.Entry<String, List<Meta.FileConfig.FileInfo>> entry : groupKeyFileInfoListMap.entrySet()) {
            //拿到同组的Meta.FileConfig.FileInfo列表集合
            List<Meta.FileConfig.FileInfo> tempFileInfoList = entry.getValue();
            //将一个组内，子文件列表展平，然后根据outputPath进行去重得到一个新的FileInfo列表
            List<Meta.FileConfig.FileInfo> newFileInfoList = new ArrayList<>(tempFileInfoList.stream().flatMap(fileInfo -> fileInfo.getFiles().stream()).collect(Collectors.toMap(Meta.FileConfig.FileInfo::getOutputPath, o -> o, (e, r) -> r)).values());
            // 使用新的 group 配置
            Meta.FileConfig.FileInfo newFileInfo = CollUtil.getLast(tempFileInfoList);
            newFileInfo.setFiles(newFileInfoList);
            resultList.add(newFileInfo);
        }
        // 4. 将未分组的文件添加到结果列表
        List<Meta.FileConfig.FileInfo> noGroupFileInfoList = fileInfoList.stream().filter(fileInfo -> StrUtil.isBlank(fileInfo.getGroupKey())).collect(Collectors.toList());
        resultList.addAll(new ArrayList<>(noGroupFileInfoList.stream().collect(Collectors.toMap(Meta.FileConfig.FileInfo::getInputPath, o -> o, (e, r) -> r)).values()));
        return resultList;
    }


    /**
     * Meta.ModelConfig.ModelInfo列表去重，支持模型分组，生成配置文件前处理
     * @param modelInfoList  传入Meta.ModelConfig.ModelInfo列表
     * @return  去重后的Meta.ModelConfig.ModelInfo列表
     */
    private static List<Meta.ModelConfig.ModelInfo> distinctModels
            (List<Meta.ModelConfig.ModelInfo> modelInfoList) {

        //1.有分组的，以组为单位划分
        Map<String, List<Meta.ModelConfig.ModelInfo>> groupKeyModelInfoListMap = modelInfoList
                .stream()
                .filter(modelInfo -> StrUtil.isNotBlank(modelInfo.getGroupKey()))
                .collect(Collectors.groupingBy(Meta.ModelConfig.ModelInfo::getGroupKey));


        List<Meta.ModelConfig.ModelInfo> resultList = new ArrayList<>();
        //2.同组内的模型配置合并
        for (Map.Entry<String, List<Meta.ModelConfig.ModelInfo>> entry : groupKeyModelInfoListMap.entrySet()) {
            //拿到同组内的模型配置
            List<Meta.ModelConfig.ModelInfo> tempModelInfoList = entry.getValue();
            // 通过展开和去重得到新的模型配置列表
            List<Meta.ModelConfig.ModelInfo> newModelInfoList = new ArrayList<>(tempModelInfoList.stream()
                    .flatMap(modelInfo -> modelInfo.getModels().stream())
                    .collect(Collectors.toMap(Meta.ModelConfig.ModelInfo::getFieldName, o -> o, (e, r) -> r))
                    .values());

            //使用新的 group 配置
            Meta.ModelConfig.ModelInfo newModelInfo = CollUtil.getLast(tempModelInfoList);
            newModelInfo.setModels(newModelInfoList);
            resultList.add(newModelInfo);
        }

        List<Meta.ModelConfig.ModelInfo> noGroupModelInfoList = modelInfoList.stream().filter(modelInfo -> StrUtil.isBlank(modelInfo.getGroupKey())).collect(Collectors.toList());
        resultList.addAll(new ArrayList<>(noGroupModelInfoList.stream().collect(Collectors.toMap(Meta.ModelConfig.ModelInfo::getFieldName, o -> o, (e, r) -> r)).values()));
        return resultList;
    }

    /**
     * 重载创建模板文件方法，支持输入封装的配置对象
     * @param templateMakerConfig
     * @return
     */
    public static long makeTemplate(TemplateMakerConfig templateMakerConfig){
        Meta meta = templateMakerConfig.getMeta();
        String originProject = templateMakerConfig.getOriginProjectPath();
        TemplateMakerFileConfig templateMakerFileConfig = templateMakerConfig.getFileConfig();
        TemplateMakerModelConfig templateMakerModelConfig = templateMakerConfig.getModelConfig();
        TemplateMakerOutputConfig templateMakerOutputConfig = templateMakerConfig.getOutputConfig();
        return makeTemplate(meta, originProject, templateMakerFileConfig, templateMakerModelConfig, templateMakerOutputConfig,templateMakerConfig.getId());
    }
}
